package cn.com.demo.cert;

import cn.com.demo.p10.PKCS10CertRequestExample;
import cn.com.demo.util.Utils;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.*;
import org.bouncycastle.jce.PKCS10CertificationRequest;
import org.bouncycastle.jce.X509Principal;
import org.bouncycastle.openssl.PEMWriter;
import org.bouncycastle.x509.X509V1CertificateGenerator;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.bouncycastle.x509.extension.AuthorityKeyIdentifierStructure;
import org.bouncycastle.x509.extension.SubjectKeyIdentifierStructure;

import javax.security.auth.x500.X500Principal;
import javax.security.auth.x500.X500PrivateCredential;
import java.io.OutputStreamWriter;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;

/**
 * Certificate ::= SEQUENCE {
 * tbsCertificate TBSCertificate,
 * signatureAlgorithm AlgorithmIdentifier,
 * signatureValue BIT STRING }
 * <p>
 * TBSCertificate ::=  SEQUENCE {
 * version         [0]  EXPLICIT Version DEFAULT v1,
 * serialNumber         CertificateSerialNumber,
 * signature            AlgorithmIdentifier,
 * issuer               Name,
 * <p>
 * validity             Validity,
 * subject              Name,
 * subjectPublicKeyInfo SubjectPublicKeyInfo,
 * issuerUniqueID  [1]  IMPLICIT UniqueIdentifier OPTIONAL,
 * -- If present, version shall be v2 or v3
 * subjectUniqueID [2]  IMPLICIT UniqueIdentifier OPTIONAL,
 * -- If present, version shall be v2 or v3
 * extensions      [3]  EXPLICIT Extensions OPTIONAL
 * -- If present, version shall be v3 }
 * <p>
 * Name ::= CHOICE { RDNSequence }
 * <p>
 * Version ::= INTEGER { v1(0), v2(1), v3(2) }
 * <p>
 * CertificateSerialNumber ::= INTEGER
 * <p>
 * Validity ::= SEQUENCE {
 * notBefore Time,
 * notAfter Time }
 * Time ::= CHOICE {
 * utcTime UTCTime,
 * generalTime GeneralizedTime }
 * <p>
 * UniqueIdentifier ::= BIT STRING
 * <p>
 * subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
 *
 * @author yuanzhenchao
 */
@SuppressWarnings("deprecation")
public class X509CertificateExample {

    private static final long VALIDITY_PERIOD = 365 * 24 * 60 * 60 * 1000;

    public static X509Certificate generateV1Certificate(
            String issuer,
            String subject,
            PublicKey publicKey,
            PrivateKey privateKey,
            String signatureAlgorithm
    )
            throws InvalidKeyException, SecurityException, SignatureException {
        X509V1CertificateGenerator certGen = new X509V1CertificateGenerator();
        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
        certGen.setIssuerDN(new X509Principal(issuer));
        certGen.setSubjectDN(new X509Principal(subject));

        Date notBefore = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(notBefore);
        calendar.add(Calendar.DAY_OF_YEAR, 365);
        Date notAfter = calendar.getTime();

        certGen.setNotBefore(notBefore);
        certGen.setNotAfter(notAfter);
        certGen.setPublicKey(publicKey);
        certGen.setSignatureAlgorithm(signatureAlgorithm);
        return certGen.generateX509Certificate(privateKey);
    }

    public static X509Certificate generateV3Certificate(
            String issuer,
            String subject,
            PublicKey publicKey,
            PrivateKey privateKey,
            String signatureAlgorithm
    )
            throws CertificateEncodingException, InvalidKeyException, IllegalStateException, NoSuchAlgorithmException, SignatureException {
        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
        certGen.setIssuerDN(new X500Principal(issuer));
        certGen.setSubjectDN(new X509Principal(subject));
        certGen.setPublicKey(publicKey);

        certGen.setNotBefore(new Date(System.currentTimeMillis()));
        certGen.setNotAfter(new Date(System.currentTimeMillis() + 500000));
        certGen.setSignatureAlgorithm(signatureAlgorithm);

        return certGen.generate(privateKey);
    }

    public static X509Certificate generateV3Certificate(PKCS10CertificationRequest request,
                                                        X509Certificate rootCertificate,
                                                        PrivateKey privateKey)
            throws Exception {
        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();
        certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis()));
        certGen.setIssuerDN(rootCertificate.getSubjectX500Principal());
        certGen.setSubjectDN(request.getCertificationRequestInfo().getSubject());
        certGen.setPublicKey(request.getPublicKey());
        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
        certGen.setNotBefore(new Date(System.currentTimeMillis()));
        certGen.setNotAfter(new Date(System.currentTimeMillis() + 500000));


        certGen.addExtension(X509Extensions.AuthorityKeyIdentifier,
                false, new AuthorityKeyIdentifierStructure(rootCertificate));
        certGen.addExtension(X509Extensions.SubjectKeyIdentifier, false,
                new SubjectKeyIdentifierStructure(request.getPublicKey("BC")));
        certGen.addExtension(X509Extensions.BasicConstraints, true, new BasicConstraints(false));
        certGen.addExtension(X509Extensions.KeyUsage, true, new KeyUsage(KeyUsage.digitalSignature
                | KeyUsage.dataEncipherment
        ));
        certGen.addExtension(X509Extensions.ExtendedKeyUsage, true,
                new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth));

        //extract the extension request attribute
        ASN1Set attributes = request.getCertificationRequestInfo().getAttributes();
        if (attributes != null) {

            for (int i = 0; i < attributes.size(); i++) {
                Attribute attribute = Attribute.getInstance(attributes.getObjectAt(i));
                //process extension request
                if (attribute.getAttrType().equals(PKCSObjectIdentifiers.pkcs_9_at_extensionRequest)) {
                    X509Extensions extensions = X509Extensions.getInstance(attribute.getAttrValues().getObjectAt(0));
                    Enumeration e = extensions.oids();
                    while (e.hasMoreElements()) {
                        DERObjectIdentifier oid = (DERObjectIdentifier) e.nextElement();
                        X509Extension ext = extensions.getExtension(oid);
                        certGen.addExtension(oid, ext.isCritical(), ext.getValue().getOctets());
                    }
                }
            }
        }

        return certGen.generate(privateKey);
    }

    public static X509Certificate[] buildCertificateChain() throws Exception {
        //generate certificate request
        KeyPair keyPair = Utils.generateRSAKeyPair(2048, Utils.createFixedRandom());
        PKCS10CertificationRequest request = PKCS10CertRequestExample.generateRequest("CN=USER", keyPair);

        //create a root certificate
        KeyPair rootKeyPair = Utils.generateRSAKeyPair(2048);
        X509Certificate rootCertificate = generateV1Certificate("CN=ROOT", "CN=ROOT", rootKeyPair.getPublic(), rootKeyPair.getPrivate(), "SHA256WithRSAEncryption");
        X509Certificate issuedCertificate = generateV3Certificate(request, rootCertificate, rootKeyPair.getPrivate());
        //verify the certification request
        if (!request.verify("BC")) {
            System.out.println("request failed to verify!");
            System.exit(1);
        }

        //create the certificate using the information in the request


        return new X509Certificate[]{issuedCertificate, rootCertificate};
    }

    /**
     * @param args
     * @throws NoSuchProviderException
     * @throws NoSuchAlgorithmException
     * @throws SignatureException
     * @throws SecurityException
     * @throws InvalidKeyException
     * @throws CertificateException
     */
    public static void main(String[] args)
            throws Exception {
		/*KeyPair keyPair = Utils.generateRSAKeyPair(1024, Utils.createFixedRandom());
		X509Certificate cert = generateV1Certificate("CN=ROOT","CN=ROOT",keyPair.getPublic(),keyPair.getPrivate(),"SHA256WithRSAEncryption");
		cert.checkValidity(new Date());
		cert.verify(cert.getPublicKey());
		System.out.println("valid certificate generated");*/

        X509Certificate[] certificates = buildCertificateChain();
        PEMWriter pemWriter = new PEMWriter(new OutputStreamWriter(System.out));
        pemWriter.writeObject(certificates[0]);
        pemWriter.writeObject(certificates[1]);
        pemWriter.close();
    }


    /**
     * Generate a sample V1 certificate to use as a CA root certificate
     */
    public static X509Certificate generateRootCert(KeyPair pair)
            throws Exception {
        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

        certGen.setSerialNumber(BigInteger.valueOf(1));
        certGen.setIssuerDN(new X500Principal("CN=ESS-PDF, O=北京数字认证股份有限公司, C=CN"));
        certGen.setNotBefore(new Date(System.currentTimeMillis()));
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.DATE, 3650);
        certGen.setNotAfter(calendar.getTime());
        certGen.setSubjectDN(new X500Principal("CN=ESS-PDF, O=北京数字认证股份有限公司, C=CN"));

        certGen.setPublicKey(pair.getPublic());
        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");
        certGen.addExtension(X509Extensions.SubjectKeyIdentifier,
                false, new SubjectKeyIdentifierStructure(pair.getPublic()));
        certGen.addExtension(X509Extensions.BasicConstraints,
                true, new BasicConstraints(0));
        certGen.addExtension(
                X509Extensions.KeyUsage, true, new KeyUsage(
                        KeyUsage.digitalSignature | KeyUsage.keyCertSign | KeyUsage.cRLSign | KeyUsage.keyEncipherment));
        return certGen.generateX509Certificate(pair.getPrivate(), "BC");
    }


    /**
     * Generate a sample V3 certificate to use as an intermediate CA certificate
     */
    public static X509Certificate generateIntermediateCert(
            PublicKey intKey, PrivateKey caKey, X509Certificate caCert)
            throws Exception {
        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

        certGen.setSerialNumber(BigInteger.valueOf(1));
        certGen.setIssuerDN(caCert.getSubjectX500Principal());
        certGen.setNotBefore(new Date(System.currentTimeMillis()));
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.add(Calendar.DATE, 3650);
        certGen.setNotAfter(calendar.getTime());
        certGen.setSubjectDN(
                new X500Principal("CN=AXTX-ESS, O=北京安信天行科技有限公司, C=CN"));
        certGen.setPublicKey(intKey);
        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");

        certGen.addExtension(
                X509Extensions.KeyUsage, true, new KeyUsage(
                        KeyUsage.digitalSignature | KeyUsage.dataEncipherment));

        return certGen.generateX509Certificate(caKey, "BC");
    }

    /**
     * Generate a sample V3 certificate to use as an end entity certificate
     */
    public static X509Certificate generateEndEntityCert(
            PublicKey entityKey, PrivateKey caKey, X509Certificate caCert)
            throws Exception {
        X509V3CertificateGenerator certGen = new X509V3CertificateGenerator();

        certGen.setSerialNumber(BigInteger.valueOf(1));
        certGen.setIssuerDN(caCert.getSubjectX500Principal());
        certGen.setNotBefore(new Date(System.currentTimeMillis()));
        certGen.setNotAfter(
                new Date(System.currentTimeMillis() + VALIDITY_PERIOD));
        certGen.setSubjectDN(new X500Principal("CN=Test End Certificate"));
        certGen.setPublicKey(entityKey);
        certGen.setSignatureAlgorithm("SHA256WithRSAEncryption");

        certGen.addExtension(X509Extensions.AuthorityKeyIdentifier,
                false, new AuthorityKeyIdentifierStructure(caCert));
        certGen.addExtension(X509Extensions.SubjectKeyIdentifier,
                false, new SubjectKeyIdentifierStructure(entityKey));
        certGen.addExtension(X509Extensions.BasicConstraints,
                true, new BasicConstraints(false));
        certGen.addExtension(X509Extensions.KeyUsage,
                true, new KeyUsage(KeyUsage.digitalSignature | KeyUsage.keyEncipherment));

        return certGen.generateX509Certificate(caKey, "BC");
    }

    public static String ROOT_ALIAS = "root";
    public static String INTERMEDIATE_ALIAS = "intermediate";
    public static String END_ALIAS = "end";

    /**
     * generate a X500PrivateCredential for the root entity
     */
    public static X500PrivateCredential generateRootCredetial() throws Exception {
        KeyPair keyPair = Utils.generateRSAKeyPair(1024);
        X509Certificate rootCert = generateRootCert(keyPair);
        return new X500PrivateCredential(rootCert, keyPair.getPrivate(), ROOT_ALIAS);
    }

    /**
     * generate a X500PrivateCredential for the intermediate entity
     */
    public static X500PrivateCredential generateIntermediateCredential(
            PrivateKey caKey,
            X509Certificate caCert
    ) throws Exception {
        KeyPair keyPair = Utils.generateRSAKeyPair(1024);
        X509Certificate IntermediateCert = generateIntermediateCert(keyPair.getPublic(), caKey, caCert);
        return new X500PrivateCredential(IntermediateCert, keyPair.getPrivate(), INTERMEDIATE_ALIAS);
    }

    /**
     * generate a X500PrivateCredential for the end entity
     */
    public static X500PrivateCredential generateEndCredential(
            PrivateKey caKey,
            X509Certificate caCert
    ) throws Exception {
        KeyPair keyPair = Utils.generateRSAKeyPair(1024);
        X509Certificate endCert = generateIntermediateCert(keyPair.getPublic(), caKey, caCert);
        return new X500PrivateCredential(endCert, keyPair.getPrivate(), END_ALIAS);
    }
}
